package com.hqd.ch03.v51.boot;

import com.hqd.ch03.v51.context.ConfigurableApplicationContext;
import com.hqd.ch03.v51.context.annotation.AnnotationConfigApplicationContext;
import com.hqd.ch03.v51.io.support.SpringFactoriesLoader;
import com.hqd.ch03.v51.utils.BeanUtils;

import java.util.function.Supplier;

@FunctionalInterface
public interface ApplicationContextFactory {

    /**
     * A default {@link ApplicationContextFactory} implementation that will create an
     * appropriate context for the {@link WebApplicationType}.
     */
    ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
            for (ApplicationContextFactory candidate : SpringFactoriesLoader
                    .loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())) {
                ConfigurableApplicationContext context = candidate.create(webApplicationType);
                if (context != null) {
                    return context;
                }
            }
            return new AnnotationConfigApplicationContext();
        } catch (Exception ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                    + "you may need a custom ApplicationContextFactory", ex);
        }
    };

    /**
     * Creates an {@code ApplicationContextFactory} that will create contexts by
     * instantiating the given {@code contextClass} via its primary constructor.
     *
     * @param contextClass the context class
     * @return the factory that will instantiate the context class
     * @see BeanUtils#instantiateClass(Class)
     */
    static ApplicationContextFactory ofContextClass(Class<? extends ConfigurableApplicationContext> contextClass) {
        return of(() -> BeanUtils.instantiateClass(contextClass));
    }

    /**
     * Creates an {@code ApplicationContextFactory} that will create contexts by calling
     * the given {@link Supplier}.
     *
     * @param supplier the context supplier, for example
     *                 {@code AnnotationConfigApplicationContext::new}
     * @return the factory that will instantiate the context class
     */
    static ApplicationContextFactory of(Supplier<ConfigurableApplicationContext> supplier) {
        return (webApplicationType) -> supplier.get();
    }

    /**
     * Creates the {@link ConfigurableApplicationContext application context} for a
     * {@link SpringApplication}, respecting the given {@code webApplicationType}.
     *
     * @param webApplicationType the web application type
     * @return the newly created application context
     */
    ConfigurableApplicationContext create(WebApplicationType webApplicationType);

}

